package com.lagou.edu.ioc.annotation;

import com.lagou.edu.ioc.AbstractApplicationContext;
import com.lagou.edu.ioc.stereotype.*;
import com.lagou.edu.ioc.support.BeanDefinition;
import com.lagou.edu.utils.ClassUtil;

import java.io.BufferedReader;
import java.io.FileReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

/**
 * Created by leon.L on 2020/1/7.
 * Desc : 注解初始化容器入口
 */
public class AnnotationConfigApplicationContext extends AbstractApplicationContext {

    /**
     * 构造方法，用于接收注解入口类
     * @param clazz
     */
    public AnnotationConfigApplicationContext(Class<?> clazz) {
        super();
        registerBean(clazz);
        refresh();
    }

    /**
     * 实现 bean 描述信息加载过程
     */
    public void registerBean(Class<?> clazz){

        String[] basePackages = null;
        Properties properties = new Properties();

        try {
            // 实例化当前配置类
            Object configInstance = clazz.getConstructor().newInstance();

            // 获取类上的注解
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == ComponentScan.class) {
                    ComponentScan componentScan = (ComponentScan) annotation;
                    basePackages = componentScan.basePackages();
                }

                if (annotation.annotationType() == PropertySource.class) {
                    PropertySource sourceFile = (PropertySource) annotation;
                    String[] propertyFile = sourceFile.value();
                    for (String file : propertyFile) {
                        readSourceFile(file, properties);
                    }
                }
            }

            // 填充属性
            Field[] configFields = clazz.getDeclaredFields();
            for (Field field : configFields) {
                field.setAccessible(true);
                String value = field.getAnnotation(Value.class).value().replaceAll("\\$\\{(.*)\\}$", "$1");
                field.set(configInstance, properties.get(value));
            }

            // 获取类中所有方法，判断方法上的注解，如果有 @Bean 注解，就把方法的返回值放到容器缓存中维护
            Method[] configMethods = clazz.getMethods();
            for (Method method : configMethods) {
                Bean beanAnnotation = method.getAnnotation(Bean.class);
                if (beanAnnotation != null) {
                    String bName = beanAnnotation.value();
                    Class<?> returnType = method.getReturnType();
                    if (bName == null) {
                        bName = returnType.getSimpleName().toLowerCase();
                    }

                    Object resultBean = method.invoke(configInstance);

                    // 放到容器中的单例集合中
                    singletonObjects.put(bName, resultBean);
                }
            }

            // 包扫描
            Set<Class<?>> beanInfoSet = new HashSet<>();
            for (String basePackage : basePackages) {
                Set<Class<?>> classSet = ClassUtil.scanPackage(basePackage, (clz) -> {
                    // 接口排除
                    if (clz.isInterface()){
                        return false;
                    }

                    // 内部类排除
                    if (clz.isAnonymousClass()) {
                        return false;
                    }

                    // 类注解不包括 Service 或者 Repository 或者 Component 的排除掉
                    if (clz.getAnnotation(Service.class) == null
                            && clz.getAnnotation(Repository.class) == null
                            && clz.getAnnotation(Component.class) == null) {
                        return false;
                    }
                    return true;
                });

                beanInfoSet.addAll(classSet);
            }

            // 加载 beanInfo 到 beanDefinitionRegistry 缓存中 (这里实现很笨，属于硬编码)
            for (Class<?> beanClass : beanInfoSet) {
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setBeanClass(beanClass.getName());

                String aliasBeanName = "";
                Component componentAnnotation = beanClass.getAnnotation(Component.class);
                if (componentAnnotation != null){
                    aliasBeanName = componentAnnotation.value();
                } else {
                    Service serviceAnnotation = beanClass.getAnnotation(Service.class);
                    if (serviceAnnotation != null) {
                        aliasBeanName = serviceAnnotation.value();
                    } else {
                        Repository repositoryAnnotation = beanClass.getAnnotation(Repository.class);
                        if (repositoryAnnotation != null) {
                            aliasBeanName = repositoryAnnotation.value();
                        }
                    }
                }


                // 没有显示设置名称，则使用类的名称
                if ("".equals(aliasBeanName)) {
                    aliasBeanName = beanClass.getName().toLowerCase();
                }
                beanDefinition.setInstanceName(aliasBeanName);

                // 获取所有带有 @Autowired 的属性
                Field[] fields = beanClass.getDeclaredFields();
                for (Field field : fields) {
                    if (field.getAnnotation(Autowired.class) != null){
                        beanDefinition.getDependencies().put(field.getName(), field.getType());
                    }
                }

                // bean 描述信息放入到 beanDefinitionRegistry 中
                beanDefinitionRegistry.put(aliasBeanName, beanDefinition);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取配置文件内容
     * @param propertyFile
     * @param properties
     */
    private void readSourceFile(String propertyFile, Properties properties) throws Exception{
        URL url = this.getClass().getClassLoader().getResource(propertyFile);
        BufferedReader br = new BufferedReader(new FileReader(url.getFile()));

        String line = br.readLine();
        while (line != null) {
            if (line.trim().length() > 0 && line.contains("=")){
                int index = line.indexOf("=");
                String[] config = line.split("=");
                String key = line.substring(0, index);
                String value = line.substring(index+1);
                properties.setProperty(key, value);
            }
            line = br.readLine();
        }
    }

}
